/*
 * Map.cpp
 *
 *  Created on: Jan 22, 2011
 *      Author: kmueller
 */

#include "MapWidget.h"

#include "MainWindow.h"
#include "Gestures.h"
#include "SearchWidget.h"
#include "../logic/Main.h"
#include "../logic/Helpers.h"
#include "../logic/Settings.h"
#include "../logic/RoutingService.h"
#include "../logic/QtImage.h"

#include "../core/Opennav.h"

#include <QtGui>
#include <QGeoCoordinate>
#include <QGeoPositionInfoSource>

using namespace QtMobility;
using namespace opennav;

MapWidget::MapWidget(MainWindow *parent) :
	QWidget(parent) {
	mainwindow = parent;

	circleGesture = new CircleGesture(this);
	connect(circleGesture, SIGNAL(turnRight()), this, SLOT(zoomIn()));
	connect(circleGesture, SIGNAL(turnLeft()), this, SLOT(zoomOut()));

	moveGesture = new MoveGesture(this);
	connect(moveGesture, SIGNAL(moved(const QPoint)), this, SLOT(moveMap(const QPoint)));
	connect(moveGesture, SIGNAL(stopped(const QPoint)), this, SLOT(moveCenter(const QPoint)));

	connect(parent, SIGNAL(increaseKeyPressed()), this, SLOT(zoomIn()));
	connect(parent, SIGNAL(decreaseKeyPressed()), this, SLOT(zoomOut()));

	const Settings &settings = Main::getInstance().getSettings();
	center = *(static_cast<QGeoCoordinate*> (settings.getCenterPosition()));
	zoomLevel = settings.getZoomLevel()->getCurrent();
	setCurrentPosition( NULL);

	connect(settings.getAutoCenter(), SIGNAL(updatePosition(const QtMobility::QGeoCoordinate&)), this, SLOT(setCenter(const QtMobility::QGeoCoordinate&)));
	connect(&Main::getInstance().getPositioningSystem(), SIGNAL(updated(const ExtendedGeoPositionInfo *)), this, SLOT(setCurrentPosition(const ExtendedGeoPositionInfo *)));

	render = new ShadowRenderer(this);
	connect(render, SIGNAL(renderingFinished()), this, SLOT(update()));
	//	mapPainter = NULL;
}

MapWidget::~MapWidget() {
	Main::getInstance().getSettings().getCenterPosition()->setValue(center);
	//	delete mapPainter;
	delete render;
}

QGeoCoordinate MapWidget::getCenter() const {
	return center;
}

void MapWidget::setCenter(const LabeledCoordinate& c) {
	setCenter(static_cast<QGeoCoordinate> (c));
}

void MapWidget::setCenter(const QGeoCoordinate& c) {
	//if (!c.isValid() || center == c)
	if(center == c)
		return;

	center = c;
	render->update();
}

void MapWidget::setCurrentPosition(const ExtendedGeoPositionInfo *pos) {
	current = pos;

	if(pos != NULL && Main::getInstance().getSettings().getRotateMap()->getCurrent() == RotateMap::ENABLED){
		render->update();
	}
	else{
		update();
	}
}

void MapWidget::moveCenter(const QPoint offset) {
	renderMutex.lock();
	QSize size = shadowFOV.getViewportSize();
	renderMutex.unlock();
	QPoint center(size.width() / 2, size.height() / 2);
	QPoint n = center - offset;
	QGeoCoordinate coord = getPointCoordinate(n);
	setCenter(coord);
}

void MapWidget::mouseDoubleClickEvent(QMouseEvent *event) {
	const QGeoCoordinate eventCoord = getEventCoordinate(event);

	QString name = QString("Map-Point: ").append(QString::number(
			eventCoord.latitude()).append(", ").append(QString::number(
			eventCoord.longitude())));
	//	QString name = "Map-Point";

	LabeledCoordinate coord(name, eventCoord);

	const RouteSettings* route = Main::getInstance().getRouteSettings();

	FullOperationDialog op(coord, this);
	connect(&op, SIGNAL(onMap(const LabeledCoordinate&)), this, SLOT(setCenter(const LabeledCoordinate&)));

	connect(&op, SIGNAL(asOrigin(const LabeledCoordinate&)), route, SLOT(setOrigin(const LabeledCoordinate&)));
	connect(&op, SIGNAL(asOrigin(const LabeledCoordinate&)), mainwindow, SLOT(showRoute()));

	connect(&op, SIGNAL(asDestination(const LabeledCoordinate&)), route, SLOT(setDestination(const LabeledCoordinate&)));
	connect(&op, SIGNAL(asDestination(const LabeledCoordinate&)), mainwindow, SLOT(showRoute()));
	op.exec();
}

QGeoCoordinate MapWidget::getEventCoordinate(QMouseEvent* event) {
	QPoint point = shadowFOV.translatePoint(event->x(), event->y());
	opennav::Point p(point.x(), point.y());
	return getPointCoordinate(p);
}

QGeoCoordinate MapWidget::getPointCoordinate(const QPoint& point) {
	QPoint tmp = shadowFOV.translatePoint(point);
	opennav::Point p(tmp.x(), tmp.y());
	return getPointCoordinate(p);
}

QGeoCoordinate MapWidget::getPointCoordinate(opennav::Point& point) {
	transMutex.lock();
	opennav::Coordinate coord = transformation.inv_transform(point);
	transMutex.unlock();

	return QGeoCoordinate(coord.dlatitude(), coord.dlongitude());
}

void MapWidget::mousePressEvent(QMouseEvent *event) {
	circleGesture->handle(event);
	moveGesture->handle(event);
}

void MapWidget::mouseMoveEvent(QMouseEvent *event) {
	circleGesture->handle(event);
	moveGesture->handle(event);
}

void MapWidget::mouseReleaseEvent(QMouseEvent *event) {
	circleGesture->handle(event);
	moveGesture->handle(event);
	render->update();
}

void MapWidget::resizeEvent(QResizeEvent * /*event*/) {
	render->update();
}

void MapWidget::moveMap(const QPoint offset) {
	renderMutex.lock();
	shadowFOV.move(offset);
	renderMutex.unlock();
	update();
}

void MapWidget::zoomIn() {
	AbstractSetting *s = Main::getInstance().getSettings().getZoomLevel();
	zoomLevel = s->getCurrent();
	zoomHandler.handle(ZoomHandler::ZoomIn, zoomLevel);
	s->setCurrent(zoomLevel);
	render->update();
}

void MapWidget::zoomOut() {
	AbstractSetting *s = Main::getInstance().getSettings().getZoomLevel();
	zoomLevel = s->getCurrent();
	zoomHandler.handle(ZoomHandler::ZoomOut, zoomLevel);
	s->setCurrent(zoomLevel);
	render->update();
}

void MapWidget::rerender() {
	render->update();
}

void MapWidget::paintEvent(QPaintEvent *event) {
	renderMutex.lock();

	QPainter mapPainter(this);
	mapPainter.setRenderHint(QPainter::Antialiasing, true);

	QPoint start = shadowFOV.getStartPoint();
	QSize size = shadowFOV.getViewportSize();
	mapPainter.drawImage(0, 0, shadow, start.x(), start.y(), size.width(),
			size.height());

	if (current != NULL) {
		// calculate, if current position is in map-view and draw point if yes
		opennav::Point p1(start.x(), start.y());
		opennav::Point p2(start.x() + size.width() - 1, start.y()
				+ size.height() - 1);
		opennav::Point p3(start.x(), start.y() + size.height() - 1);
		opennav::Point p4(start.x() + size.width() - 1, 0);

		transMutex.lock();
		opennav::Coordinate c1 = transformation.inv_transform(p1);
		opennav::Coordinate c2 = transformation.inv_transform(p2);
		opennav::Coordinate c3 = transformation.inv_transform(p3);
		opennav::Coordinate c4 = transformation.inv_transform(p4);
		transMutex.unlock();

		opennav::Area bounds;
		bounds.add_to_bounds(c1);
		bounds.add_to_bounds(c2);
		bounds.add_to_bounds(c3);
		bounds.add_to_bounds(c4);

		opennav::Coordinate coord(current->coordinate().longitude(),
				current->coordinate().latitude());

		if (bounds.contains(coord)) {
			transMutex.lock();
			opennav::Point posPoint = transformation.transform(coord);
			transMutex.unlock();

			mapPainter.setPen(QPen(QBrush(Qt::red), 15, Qt::SolidLine,
					Qt::RoundCap));
			mapPainter.drawPoint(posPoint.x, posPoint.y);

			if (Main::getInstance().getSettings().getShowVelocityVector()->getCurrent()
					== ShowVelocityVector::ENABLED) {
				// TODO: draw velocity vector
				//	mapPainter.setPen(QPen(QBrush(Qt:black, 2)));
				//	mapPainter.drawLine(posPoint.x, posPoint.y, )
			}
		}
	}

	renderMutex.unlock();
}

/*
 * FieldOfView
 * for handling the part of the shadow image which shall be painted on the screen
 */
MapWidget::FieldOfView::FieldOfView() {

}

void MapWidget::FieldOfView::setCenter(const QPoint& c) {
	center = c;
}

void MapWidget::FieldOfView::setCenter(const int x, const int y) {
	QPoint p(x, y);
	setCenter(p);
}

void MapWidget::FieldOfView::move(const QPoint& offset) {
	center -= offset;
}

void MapWidget::FieldOfView::setViewportSize(const QSize& s) {
	viewportSize = s;
}

void MapWidget::FieldOfView::setViewportSize(const int width, const int height) {
	QSize s(width, height);
	setViewportSize(s);
}

void MapWidget::FieldOfView::setOriginalSize(const QSize& s) {
	originalSize = s;
}

void MapWidget::FieldOfView::setOriginalSize(const int width, const int height) {
	QSize s(width, height);
	setOriginalSize(s);
}

QPoint MapWidget::FieldOfView::getStartPoint() const {
	QPoint start(center.x() - viewportSize.width() / 2, center.y() - viewportSize.height() / 2);
	return start;
}
QSize MapWidget::FieldOfView::getViewportSize() const {
	return viewportSize;
}

QPoint MapWidget::FieldOfView::translatePoint(const int x, const int y){
	QPoint point(x, y);
	return translatePoint(point);
}

QPoint MapWidget::FieldOfView::translatePoint(const QPoint& point){
	QPoint offset ((originalSize.width() - viewportSize.width()) / 2 , (originalSize.height() - viewportSize.height()) / 2);
	QPoint p = point + offset;
	return p;
}

/*
 * ZoomHandler
 */
MapWidget::ZoomHandler::ZoomHandler() {
	start = QDateTime::currentDateTime();
	timer.setSingleShot(true);
	//	connect(&timer, SIGNAL(timeout()), render, SLOT(update()));
}
void MapWidget::ZoomHandler::handle(ZoomDirection direction, int& zoomLevel) {
	AbstractSetting *s = Main::getInstance().getSettings().getZoomLevel();
	const QDateTime current = QDateTime::currentDateTime();
	qint64 diff = start.msecsTo(current);

	if (!timer.isActive()) {
		start = current;
		diff = 0;
	}

	timer.start(500);

	int zoomLevelOffset = 1;
	if (diff < 500)
		zoomLevelOffset = 20;
	else if (diff < 1500)
		zoomLevelOffset = 40;
	else if (diff < 3000)
		zoomLevelOffset = 70;
	else
		zoomLevelOffset = 150;

	switch (direction) {
	case ZoomIn: {
		zoomLevel = qBound(s->getMinimum(), zoomLevel - zoomLevelOffset,
				s->getMaximum());
	}
		break;

	case ZoomOut: {
		zoomLevel = qBound(s->getMinimum(), zoomLevel + zoomLevelOffset,
				s->getMaximum());
	}
		break;
	}

	// TODO: implement zooming on shadow map and enable timeout connection in construction
	//	(diff < 50) ? update() : render->update();
}

/*
 * ShadowRenderer
 * rendering map under usage of a new thread and store it in memory (shadow image)
 */
ShadowRenderer::ShadowRenderer(MapWidget* parent) :
	QThread(parent) {

	renderer = new MapRenderer();

	start();
	setPriority(QThread::IdlePriority);
}

ShadowRenderer::~ShadowRenderer() {
	terminate();
	delete renderer;
	wait();
}

void ShadowRenderer::update() {
	mutex.lock();
	update_ = true;
	mutex.unlock();
}

void ShadowRenderer::run() {
	while (isRunning()) {
		mutex.lock();
		if (update_) {
			MapWidget* p = static_cast<MapWidget*> (parent());

			p->renderMutex.lock();

			Main& main = Main::getInstance();
			int factor = 2;
			switch (main.getSettings().getShadowMapSize()->getCurrent()) {
			case ShadowMapSize::PERFORMANCE:
				factor = 1;
				break;
			case ShadowMapSize::OPTIMIZED:
				factor = 2;
				break;
			case ShadowMapSize::VISIBILENESS:
				factor = 3;
				break;
			}

			const int width = p->width() * factor;
			const int height = p->height() * factor;

			QImage tmp(width, height, QImage::Format_RGB32);
			p->shadow = tmp;

			QPainter painter(&p->shadow);
			painter.setRenderHint(QPainter::Antialiasing, true);

			opennav::Point screenCenter(width / 2, height / 2);
			opennav::Coordinate centerCoord(p->center.longitude(),
					p->center.latitude());

			int angle = p->current != NULL && main.getSettings().getRotateMap()->getCurrent() == RotateMap::ENABLED ?
					p->current->getMovingDirection(): 0;

			p->transMutex.lock();
			p->transformation.set_scale(p->zoomLevel);
			p->transformation.set_map_center(centerCoord);
			p->transformation.set_rotation(angle);
			p->transformation.set_screen_center(screenCenter);
			p->transformation.setup();

			opennav::Point p1(0, 0);
			opennav::Point p2(width - 1, height - 1);
			opennav::Point p3(0, height - 1);
			opennav::Point p4(width - 1, 0);
			opennav::Coordinate c1 = p->transformation.inv_transform(p1);
			opennav::Coordinate c2 = p->transformation.inv_transform(p2);
			opennav::Coordinate c3 = p->transformation.inv_transform(p3);
			opennav::Coordinate c4 = p->transformation.inv_transform(p4);
			p->transMutex.unlock();

			opennav::Area bounds;
			bounds.add_to_bounds(c1);
			bounds.add_to_bounds(c2);
			bounds.add_to_bounds(c3);
			bounds.add_to_bounds(c4);

			int lod = 24 - (log2(p->zoomLevel) - 8);

			opennav::MapSelection selection;

			opennav::Map* map = &Main::getInstance().getMap();
			map->create_selection(bounds, lod, selection);

			QtImage img(&painter, width, height);
			renderer->render(selection, p->transformation, img, Main::getInstance().getMap().get_route());

			painter.end();

			p->shadowFOV.setCenter(width / 2, height / 2);
			p->shadowFOV.setViewportSize(p->width(), p->height());
			p->shadowFOV.setOriginalSize(p->shadow.width(), p->shadow.height());

			p->renderMutex.unlock();

			emit renderingFinished();

			update_ = false;
		}
		mutex.unlock();
		msleep(100);
	}
}
